Line data Source code
1 : /* SPDX-License-Identifier: MIT OR GPL-3.0-only */
2 : /* sock.c
3 : ** strophe XMPP client library -- socket abstraction implementation
4 : **
5 : ** Copyright (C) 2005-2009 Collecta, Inc.
6 : **
7 : ** This software is provided AS-IS with no warranty, either express
8 : ** or implied.
9 : **
10 : ** This program is dual licensed under the MIT or GPLv3 licenses.
11 : */
12 :
13 : /** @file
14 : * Socket abstraction.
15 : */
16 :
17 : #include <stdio.h>
18 : #include <stdlib.h>
19 : #include <string.h>
20 : #include <sys/types.h>
21 :
22 : #ifdef _WIN32
23 : #include <winsock2.h>
24 : #include <ws2tcpip.h>
25 : #include <iphlpapi.h>
26 : #include <mstcpip.h> /* tcp_keepalive */
27 : #else
28 : #include <arpa/inet.h>
29 : #include <errno.h>
30 : #include <unistd.h>
31 : #include <sys/socket.h>
32 : #include <netinet/in.h>
33 : #include <netinet/tcp.h>
34 : #include <netdb.h>
35 : #include <fcntl.h>
36 : #endif
37 :
38 : #include "common.h"
39 : #include "resolver.h"
40 :
41 : const struct conn_interface sock_intf = {
42 : sock_read,
43 : sock_write,
44 : /* no flush */
45 : conn_int_nop,
46 : /* no pending */
47 : conn_int_nop,
48 : sock_error,
49 : sock_is_recoverable,
50 : NULL,
51 : };
52 :
53 : struct _xmpp_sock_t {
54 : xmpp_ctx_t *ctx;
55 : xmpp_conn_t *conn;
56 : struct addrinfo *ainfo_list;
57 : struct addrinfo *ainfo_cur;
58 : resolver_srv_rr_t *srv_rr_list;
59 : resolver_srv_rr_t *srv_rr_cur;
60 : const char *host;
61 : unsigned short port;
62 : };
63 :
64 2 : void sock_initialize(void)
65 : {
66 : #ifdef _WIN32
67 : WSADATA wsad;
68 : WSAStartup(0x0101, &wsad);
69 : #endif
70 2 : }
71 :
72 2 : void sock_shutdown(void)
73 : {
74 : #ifdef _WIN32
75 : WSACleanup();
76 : #endif
77 2 : }
78 :
79 0 : int sock_error(struct conn_interface *intf)
80 : {
81 0 : UNUSED(intf);
82 : #ifdef _WIN32
83 : return WSAGetLastError();
84 : #else
85 0 : return errno;
86 : #endif
87 : }
88 :
89 : static int _in_progress(int error)
90 : {
91 : #ifdef _WIN32
92 : return (error == WSAEWOULDBLOCK || error == WSAEINPROGRESS);
93 : #else
94 : return (error == EINPROGRESS);
95 : #endif
96 : }
97 :
98 0 : static void sock_getaddrinfo(xmpp_sock_t *xsock)
99 : {
100 0 : char service[6];
101 0 : struct addrinfo hints;
102 0 : int rc;
103 :
104 0 : if (xsock->ainfo_list) {
105 0 : freeaddrinfo(xsock->ainfo_list);
106 0 : xsock->ainfo_list = NULL;
107 : }
108 :
109 0 : if (xsock->srv_rr_cur) {
110 : /* Cache host and port for debug logs. */
111 0 : xsock->host = xsock->srv_rr_cur->target;
112 0 : xsock->port = xsock->srv_rr_cur->port;
113 :
114 0 : strophe_snprintf(service, 6, "%u", xsock->srv_rr_cur->port);
115 0 : memset(&hints, 0, sizeof(struct addrinfo));
116 0 : hints.ai_family = AF_UNSPEC;
117 : #ifdef AI_ADDRCONFIG
118 0 : hints.ai_flags = AI_ADDRCONFIG;
119 : #endif /* AI_ADDRCONFIG */
120 0 : hints.ai_protocol = IPPROTO_TCP;
121 0 : hints.ai_socktype = SOCK_STREAM;
122 :
123 0 : rc = getaddrinfo(xsock->srv_rr_cur->target, service, &hints,
124 : &xsock->ainfo_list);
125 0 : if (rc != 0) {
126 0 : strophe_debug(xsock->ctx, "sock",
127 : "getaddrinfo() failed with %s (%d)", gai_strerror(rc),
128 : rc);
129 0 : xsock->ainfo_list = NULL;
130 : }
131 : }
132 :
133 0 : xsock->ainfo_cur = xsock->ainfo_list;
134 0 : }
135 :
136 0 : xmpp_sock_t *sock_new(xmpp_conn_t *conn,
137 : const char *domain,
138 : const char *host,
139 : unsigned short port)
140 : {
141 0 : xmpp_ctx_t *ctx = conn->ctx;
142 0 : xmpp_sock_t *xsock;
143 0 : int found = XMPP_DOMAIN_NOT_FOUND;
144 :
145 0 : xsock = strophe_alloc(ctx, sizeof(*xsock));
146 0 : if (!xsock) {
147 : return NULL;
148 : }
149 :
150 0 : xsock->ctx = ctx;
151 0 : xsock->conn = conn;
152 0 : xsock->host = NULL;
153 0 : xsock->port = 0;
154 :
155 0 : if (!host) {
156 0 : found = resolver_srv_lookup(ctx, "xmpp-client", "tcp", domain,
157 : &xsock->srv_rr_list);
158 0 : if (XMPP_DOMAIN_NOT_FOUND == found)
159 0 : strophe_debug(ctx, "sock",
160 : "SRV lookup failed, connecting via domain.");
161 : }
162 0 : if (XMPP_DOMAIN_NOT_FOUND == found) {
163 : /* Resolution failed or the host is provided explicitly. */
164 0 : xsock->srv_rr_list =
165 0 : resolver_srv_rr_new(ctx, host ? host : domain, port, 0, 0);
166 : }
167 0 : xsock->srv_rr_cur = xsock->srv_rr_list;
168 :
169 0 : xsock->ainfo_list = NULL;
170 0 : sock_getaddrinfo(xsock);
171 0 : if (xsock->srv_rr_cur)
172 0 : xsock->srv_rr_cur = xsock->srv_rr_cur->next;
173 :
174 : return xsock;
175 : }
176 :
177 8 : void sock_free(xmpp_sock_t *xsock)
178 : {
179 8 : if (!xsock)
180 : return;
181 :
182 0 : if (xsock->ainfo_list)
183 0 : freeaddrinfo(xsock->ainfo_list);
184 0 : if (xsock->srv_rr_list)
185 0 : resolver_srv_free(xsock->ctx, xsock->srv_rr_list);
186 0 : strophe_free(xsock->ctx, xsock);
187 : }
188 :
189 0 : static const char *_sockaddr2str(struct sockaddr *sa, char *buf, size_t buflen)
190 : {
191 0 : buf[0] = '\0';
192 :
193 0 : switch (sa->sa_family) {
194 0 : case AF_INET:
195 0 : inet_ntop(AF_INET, &((struct sockaddr_in *)sa)->sin_addr, buf, buflen);
196 0 : break;
197 0 : case AF_INET6:
198 0 : inet_ntop(AF_INET6, &((struct sockaddr_in6 *)sa)->sin6_addr, buf,
199 : buflen);
200 0 : break;
201 : default:
202 0 : strophe_snprintf(buf, buflen, "<Unknown>");
203 : }
204 0 : return buf;
205 : }
206 :
207 0 : sock_t sock_connect(xmpp_sock_t *xsock)
208 : {
209 0 : struct addrinfo *ainfo;
210 0 : sock_t sock;
211 0 : int rc;
212 0 : char buf[64];
213 :
214 0 : do {
215 0 : if (!xsock->ainfo_cur) {
216 0 : sock_getaddrinfo(xsock);
217 0 : if (xsock->srv_rr_cur)
218 0 : xsock->srv_rr_cur = xsock->srv_rr_cur->next;
219 : }
220 0 : if (!xsock->ainfo_cur) {
221 : /* We tried all available addresses. */
222 0 : return INVALID_SOCKET;
223 : }
224 :
225 0 : ainfo = xsock->ainfo_cur;
226 0 : strophe_debug(xsock->ctx, "sock", "Connecting to %s:%u via %s",
227 0 : xsock->host, xsock->port,
228 : _sockaddr2str(ainfo->ai_addr, buf, sizeof(buf)));
229 :
230 0 : sock = socket(ainfo->ai_family, ainfo->ai_socktype, ainfo->ai_protocol);
231 0 : if (sock != INVALID_SOCKET) {
232 0 : rc = 0;
233 0 : if (xsock->conn->sockopt_cb) {
234 : /* Don't allow user to overwrite sockfd value. */
235 0 : sock_t sock_copy = sock;
236 0 : rc = xsock->conn->sockopt_cb(xsock->conn, &sock_copy);
237 0 : if (rc != 0) {
238 0 : strophe_debug(xsock->ctx, "sock",
239 : "User's setsockopt callback"
240 : " failed with %d (errno=%d)",
241 0 : rc, errno);
242 : }
243 : }
244 0 : if (rc == 0)
245 0 : rc = sock_set_nonblocking(sock);
246 0 : if (rc == 0)
247 0 : rc = connect(sock, ainfo->ai_addr, ainfo->ai_addrlen);
248 : /* Assume only connect() can cause "in progress" error. */
249 0 : if (rc != 0 && !_in_progress(sock_error(NULL))) {
250 0 : sock_close(sock);
251 : sock = INVALID_SOCKET;
252 : }
253 : }
254 0 : strophe_debug(xsock->ctx, "sock", "sock_connect() result %d", sock);
255 :
256 0 : xsock->ainfo_cur = xsock->ainfo_cur->ai_next;
257 0 : } while (sock == INVALID_SOCKET);
258 :
259 : return sock;
260 : }
261 :
262 0 : int sock_set_keepalive(sock_t sock,
263 : int timeout,
264 : int interval,
265 : int count,
266 : unsigned int user_timeout)
267 : {
268 0 : int ret;
269 0 : int optval = (timeout && interval) ? 1 : 0;
270 :
271 0 : UNUSED(count);
272 0 : UNUSED(user_timeout);
273 :
274 : #ifdef _WIN32
275 : struct tcp_keepalive ka;
276 : DWORD dw = 0;
277 :
278 : ka.onoff = optval;
279 : ka.keepalivetime = timeout * 1000;
280 : ka.keepaliveinterval = interval * 1000;
281 : ret = WSAIoctl(sock, SIO_KEEPALIVE_VALS, &ka, sizeof(ka), NULL, 0, &dw,
282 : NULL, NULL);
283 : #else
284 0 : ret = setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
285 0 : if (ret < 0)
286 : return ret;
287 :
288 0 : if (optval) {
289 : #ifdef TCP_KEEPIDLE
290 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &timeout,
291 : sizeof(timeout));
292 : #elif defined(TCP_KEEPALIVE)
293 : /* QNX receives `struct timeval' as argument, but it seems OSX does int
294 : */
295 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPALIVE, &timeout,
296 : sizeof(timeout));
297 : #endif /* TCP_KEEPIDLE */
298 0 : if (ret < 0)
299 : return ret;
300 : #ifdef TCP_KEEPINTVL
301 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &interval,
302 : sizeof(interval));
303 0 : if (ret < 0)
304 : return ret;
305 : #endif /* TCP_KEEPINTVL */
306 : }
307 :
308 0 : if (count) {
309 : #ifdef TCP_KEEPCNT
310 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &count, sizeof(count));
311 0 : if (ret < 0)
312 : return ret;
313 : #endif /* TCP_KEEPCNT */
314 : }
315 :
316 0 : if (user_timeout) {
317 : #ifdef TCP_USER_TIMEOUT
318 0 : ret = setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &user_timeout,
319 : sizeof(user_timeout));
320 0 : if (ret < 0)
321 : return ret;
322 : #elif defined(TCP_RXT_CONNDROPTIME)
323 : int rxt = user_timeout / 1000;
324 : ret = setsockopt(sock, IPPROTO_TCP, TCP_RXT_CONNDROPTIME, &rxt,
325 : sizeof(rxt));
326 : if (ret < 0)
327 : return ret;
328 : #endif /* TCP_USER_TIMEOUT */
329 : }
330 :
331 : #endif /* _WIN32 */
332 :
333 : return ret;
334 : }
335 :
336 : /** Example sockopt callback function
337 : * An example function that can be used to set reasonable default keepalive
338 : * options on sockets when registered for a connection with
339 : * xmpp_conn_set_sockopt_callback()
340 : *
341 : * @param conn a Strophe connection object
342 : * @param socket pointer to a socket descriptor
343 : *
344 : * @see xmpp_sockopt_callback for details on the `socket` parameter
345 : * @ingroup Connections
346 : */
347 0 : int xmpp_sockopt_cb_keepalive(xmpp_conn_t *conn, void *socket)
348 : {
349 0 : sock_t sock = *((sock_t *)socket);
350 :
351 0 : return sock_set_keepalive(
352 : sock, conn->ka_timeout, conn->ka_interval, conn->ka_count,
353 : conn->ka_count
354 0 : ? (conn->ka_timeout + conn->ka_interval * conn->ka_count) * 1000
355 : : 0);
356 : }
357 :
358 0 : int sock_close(sock_t sock)
359 : {
360 : #ifdef _WIN32
361 : return closesocket(sock);
362 : #else
363 0 : return close(sock);
364 : #endif
365 : }
366 :
367 0 : static int _sock_set_blocking_mode(sock_t sock, int blocking)
368 : {
369 : #ifdef _WIN32
370 : u_long nonblock = blocking ? 0 : 1;
371 : return ioctlsocket(sock, FIONBIO, &nonblock);
372 : #else
373 0 : int rc;
374 :
375 0 : rc = fcntl(sock, F_GETFL, NULL);
376 0 : if (rc >= 0) {
377 0 : rc = blocking ? rc & (~O_NONBLOCK) : rc | O_NONBLOCK;
378 0 : rc = fcntl(sock, F_SETFL, rc);
379 : }
380 0 : return rc;
381 : #endif
382 : }
383 :
384 0 : int sock_set_blocking(sock_t sock)
385 : {
386 0 : return _sock_set_blocking_mode(sock, 1);
387 : }
388 :
389 0 : int sock_set_nonblocking(sock_t sock)
390 : {
391 0 : return _sock_set_blocking_mode(sock, 0);
392 : }
393 :
394 0 : int sock_read(struct conn_interface *intf, void *buff, size_t len)
395 : {
396 0 : return recv(intf->conn->sock, buff, len, 0);
397 : }
398 :
399 0 : int sock_write(struct conn_interface *intf, const void *buff, size_t len)
400 : {
401 0 : return send(intf->conn->sock, buff, len, 0);
402 : }
403 :
404 0 : int sock_is_recoverable(struct conn_interface *intf, int error)
405 : {
406 0 : UNUSED(intf);
407 : #ifdef _WIN32
408 : return (error == WSAEINTR || error == WSAEWOULDBLOCK ||
409 : error == WSAEINPROGRESS);
410 : #else
411 0 : return (error == EAGAIN || error == EINTR);
412 : #endif
413 : }
414 :
415 0 : int sock_connect_error(sock_t sock)
416 : {
417 0 : struct sockaddr_storage ss;
418 0 : struct sockaddr *sa = (struct sockaddr *)&ss;
419 0 : socklen_t len;
420 0 : char temp;
421 :
422 0 : memset(&ss, 0, sizeof(ss));
423 0 : len = sizeof(ss);
424 0 : sa->sa_family = AF_UNSPEC;
425 :
426 : /* we don't actually care about the peer name, we're just checking if
427 : * we're connected or not */
428 0 : if (getpeername(sock, sa, &len) == 0) {
429 0 : return 0;
430 : }
431 :
432 : /* it's possible that the error wasn't ENOTCONN, so if it wasn't,
433 : * return that */
434 : #ifdef _WIN32
435 : if (sock_error(NULL) != WSAENOTCONN)
436 : return sock_error(NULL);
437 : #else
438 0 : if (sock_error(NULL) != ENOTCONN)
439 0 : return sock_error(NULL);
440 : #endif
441 :
442 : /* load the correct error into errno through error slippage */
443 0 : recv(sock, &temp, 1, 0);
444 :
445 0 : return sock_error(NULL);
446 : }
|